// ========================================================================
// Copyright (c) 2009 Intalio, Inc.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
// The Eclipse Public License is available at 
// http://www.eclipse.org/legal/epl-v10.html
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
// You may elect to redistribute this code under either of these licenses. 
// ========================================================================
package org.eclipse.jetty.osgi.boot.internal.webapp;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import org.eclipse.jetty.osgi.boot.JettyBootstrapActivator;
import org.eclipse.jetty.osgi.boot.OSGiServerConstants;
import org.eclipse.jetty.osgi.boot.OSGiWebappConstants;
import org.eclipse.jetty.osgi.boot.internal.serverfactory.IManagedJettyServerRegistry;
import org.eclipse.jetty.osgi.boot.internal.serverfactory.ServerInstanceWrapper;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.Scanner;
import org.eclipse.jetty.webapp.WebAppContext;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceReference;

/**
 * When a {@link ContextHandler} service is activated we look into it and if the
 * corresponding webapp is actually not configured then we go and register it.
 * <p>
 * The idea is to always go through this class when we deploy a new webapp on
 * jetty.
 * </p>
 * <p>
 * We are exposing each web-application as an OSGi service. This lets us update
 * the webapps and stop/start them directly at the OSGi layer. It also give us
 * many ways to declare those services: Declarative Services for example. <br/>
 * It is a bit different from the way the HttpService works where we would have
 * a WebappService and we woud register a webapp onto it. <br/>
 * It does not go against RFC-66 nor does it prevent us from supporting the
 * WebappContainer semantics.
 * </p>
 */
public class JettyContextHandlerServiceTracker implements ServiceListener
{

	/** New style: ability to manage multiple jetty instances */
	private final IManagedJettyServerRegistry _registry;

    /** The context-handler to deactivate indexed by context handler */
    private Map<ServiceReference, ContextHandler> _indexByServiceReference = new HashMap<ServiceReference, ContextHandler>();

    /**
     * The index is the bundle-symbolic-name/path/to/context/file when there is such thing
     */
    private Map<String, ServiceReference> _indexByContextFile = new HashMap<String, ServiceReference>();

    /** in charge of detecting changes in the osgi contexts home folder. */
    private Scanner _scanner;

    /**
     * @param registry
     */
    public JettyContextHandlerServiceTracker(IManagedJettyServerRegistry registry) throws Exception
    {
    	_registry = registry;
    }

    public void stop()
    {
        if (_scanner != null)
        {
            _scanner.stop();
        }
        // the class that created the server is also in charge of stopping it.
        // nothing to stop in the WebappRegistrationHelper

    }
    
    /**
     * @param contextHome Parent folder where the context files can override the context files
     * defined in the web bundles: equivalent to the contexts folder in a traditional
     * jetty installation.
     * when null, just do nothing.
     */
    protected void setupContextHomeScanner(File contextHome) throws IOException
    {
        if (contextHome == null)
        {
        	return;
        }
        final String osgiContextHomeFolderCanonicalPath = contextHome.getCanonicalPath();
        _scanner = new Scanner();
        _scanner.setRecursive(true);
        _scanner.setReportExistingFilesOnStartup(false);
        _scanner.addListener(new Scanner.DiscreteListener()
        {
            public void fileAdded(String filename) throws Exception
            {
                // adding a file does not create a new app,
                // it just reloads it with the new custom file.
                // well, if the file does not define a context handler,
                // then in fact it does remove it.
                reloadJettyContextHandler(filename, osgiContextHomeFolderCanonicalPath);
            }

            public void fileChanged(String filename) throws Exception
            {
                reloadJettyContextHandler(filename, osgiContextHomeFolderCanonicalPath);
            }

            public void fileRemoved(String filename) throws Exception
            {
                // removing a file does not remove the app:
                // it just goes back to the default embedded in the bundle.
                // well, if there was no default then it does remove it.
                reloadJettyContextHandler(filename, osgiContextHomeFolderCanonicalPath);
            }
        });

    }

    /**
     * Receives notification that a service has had a lifecycle change.
     * 
     * @param ev
     *            The <code>ServiceEvent</code> object.
     */
    public void serviceChanged(ServiceEvent ev)
    {
        ServiceReference sr = ev.getServiceReference();
        switch (ev.getType())
        {
            case ServiceEvent.MODIFIED:
            case ServiceEvent.UNREGISTERING:
            {
                ContextHandler ctxtHandler = unregisterInIndex(ev.getServiceReference());
                if (ctxtHandler != null && !ctxtHandler.isStopped())
                {
                    try
                    {
                    	getWebBundleDeployerHelp(sr).unregister(ctxtHandler);
                    }
                    catch (Exception e)
                    {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
            if (ev.getType() == ServiceEvent.UNREGISTERING)
            {
                break;
            }
            else
            {
                // modified, meaning: we reload it. now that we stopped it;
                // we can register it.
            }
            case ServiceEvent.REGISTERED:
            {
                Bundle contributor = sr.getBundle();
                BundleContext context = FrameworkUtil.getBundle(JettyBootstrapActivator.class).getBundleContext();
                ContextHandler contextHandler = (ContextHandler)context.getService(sr);
                if (contextHandler.getServer() != null)
                {
                    // is configured elsewhere.
                    return;
                }
                String contextFilePath = (String)sr.getProperty(OSGiWebappConstants.SERVICE_PROP_CONTEXT_FILE_PATH);
                if (contextHandler instanceof WebAppContext && contextFilePath == null)
                //it could be a web-application that will in fact be configured via a context file.
                //that case is identified by the fact that the contextFilePath is not null
                //in that case we must use the register context methods.
                {
                    WebAppContext webapp = (WebAppContext)contextHandler;
                    String contextPath = (String)sr.getProperty(OSGiWebappConstants.SERVICE_PROP_CONTEXT_PATH);
                    if (contextPath == null)
                    {
                        contextPath = webapp.getContextPath();
                    }
                    String webXmlPath = (String)sr.getProperty(OSGiWebappConstants.SERVICE_PROP_WEB_XML_PATH);
                    if (webXmlPath == null)
                    {
                        webXmlPath = webapp.getDescriptor();
                    }
                    String defaultWebXmlPath = (String)sr.getProperty(OSGiWebappConstants.SERVICE_PROP_DEFAULT_WEB_XML_PATH);
                    if (defaultWebXmlPath == null)
                    {
                        defaultWebXmlPath = webapp.getDefaultsDescriptor();
                    }
                    String war = (String)sr.getProperty("war");
                    try
                    {
                    	IWebBundleDeployerHelper deployerHelper = getWebBundleDeployerHelp(sr);
                    	if (deployerHelper == null)
                    	{
                    		
                    	}
                    	else
                    	{
                    		WebAppContext handler = deployerHelper
                        		.registerWebapplication(contributor,war,contextPath,
                        		(String)sr.getProperty(OSGiWebappConstants.SERVICE_PROP_EXTRA_CLASSPATH),
                        		(String)sr.getProperty(OSGiWebappConstants.SERVICE_PROP_BUNDLE_INSTALL_LOCATION_OVERRIDE),
                        		(String)sr.getProperty(OSGiWebappConstants.SERVICE_PROP_REQUIRE_TLD_BUNDLE),
                                webXmlPath,defaultWebXmlPath,webapp);
                            if (handler != null)
                            {
                                registerInIndex(handler,sr);
                            }
                    	}
                    }
                    catch (Throwable e)
                    {
                        e.printStackTrace();
                    }
                }
                else
                {
                    // consider this just an empty skeleton:
                    if (contextFilePath == null)
                    {
                        throw new IllegalArgumentException("the property contextFilePath is required");
                    }
                    try
                    {
                    	IWebBundleDeployerHelper deployerHelper = getWebBundleDeployerHelp(sr);
                    	if (deployerHelper == null)
                    	{
                    		//more warnings?
                    	}
                    	else
                    	{
                    		if (Boolean.TRUE.toString().equals(sr.getProperty(
                    				IWebBundleDeployerHelper.INTERNAL_SERVICE_PROP_UNKNOWN_CONTEXT_HANDLER_TYPE)))
                    		{
                    			contextHandler = null;
                    		}
	                        ContextHandler handler = deployerHelper.registerContext(contributor,contextFilePath,
	                        		(String)sr.getProperty(OSGiWebappConstants.SERVICE_PROP_EXTRA_CLASSPATH),
	                        		(String)sr.getProperty(OSGiWebappConstants.SERVICE_PROP_BUNDLE_INSTALL_LOCATION_OVERRIDE),
	                        		(String)sr.getProperty(OSGiWebappConstants.SERVICE_PROP_REQUIRE_TLD_BUNDLE),
	                                contextHandler);
	                        if (handler != null)
	                        {
	                            registerInIndex(handler,sr);
	                        }
                    	}
                    }
                    catch (Throwable e)
                    {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
                break;
        }
    }

    private void registerInIndex(ContextHandler handler, ServiceReference sr)
    {
        _indexByServiceReference.put(sr,handler);
        String key = getSymbolicNameAndContextFileKey(sr);
        if (key != null)
        {
            _indexByContextFile.put(key,sr);
        }
    }

    /**
     * Returns the ContextHandler to stop.
     * 
     * @param reg
     * @return the ContextHandler to stop.
     */
    private ContextHandler unregisterInIndex(ServiceReference sr)
    {
        ContextHandler handler = _indexByServiceReference.remove(sr);
        String key = getSymbolicNameAndContextFileKey(sr);
        if (key != null)
        {
            _indexByContextFile.remove(key);
        }
        if (handler == null)
        {
            // a warning?
            return null;
        }
        return handler;
    }

    /**
     * @param sr
     * @return The key for a context file within the osgi contexts home folder.
     */
    private String getSymbolicNameAndContextFileKey(ServiceReference sr)
    {
        String contextFilePath = (String)sr.getProperty(OSGiWebappConstants.SERVICE_PROP_CONTEXT_FILE_PATH);
        if (contextFilePath != null)
        {
            return sr.getBundle().getSymbolicName() + "/" + contextFilePath;
        }
        return null;
    }

    /**
     * Called by the scanner when one of the context files is changed.
     * 
     * @param contextFileFully
     */
    public void reloadJettyContextHandler(String canonicalNameOfFileChanged, String osgiContextHomeFolderCanonicalPath)
    {
        String key = getNormalizedRelativePath(canonicalNameOfFileChanged, osgiContextHomeFolderCanonicalPath);
        if (key == null)
        {
            return;
        }
        ServiceReference sr = _indexByContextFile.get(key);
        if (sr == null)
        {
            // nothing to do?
            return;
        }
        serviceChanged(new ServiceEvent(ServiceEvent.MODIFIED,sr));
    }

    /**
     * @param canFilename
     * @return
     */
    private String getNormalizedRelativePath(String canFilename, String osgiContextHomeFolderCanonicalPath)
    {
        if (!canFilename.startsWith(osgiContextHomeFolderCanonicalPath))
        {
            // why are we here: this does not look like a child of the osgi
            // contexts home.
            // warning?
            return null;
        }
        return canFilename.substring(osgiContextHomeFolderCanonicalPath.length()).replace('\\','/');
    }
    
    /**
     * @return The server on which this webapp is meant to be deployed
     */
    private ServerInstanceWrapper getServerInstanceWrapper(String managedServerName)
    {
    	if (_registry == null)
    	{
    		return null;
    	}
    	if (managedServerName == null)
    	{
    		managedServerName = OSGiServerConstants.MANAGED_JETTY_SERVER_DEFAULT_NAME;
    	}
    	ServerInstanceWrapper wrapper = _registry.getServerInstanceWrapper(managedServerName);
    	//System.err.println("Returning " + managedServerName + "  = " + wrapper);
    	return wrapper;
    }
    
    private IWebBundleDeployerHelper getWebBundleDeployerHelp(ServiceReference sr)
    {
    	if (_registry == null)
    	{
    		return null;
    	}
    	String managedServerName = (String)sr.getProperty(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME);
    	ServerInstanceWrapper wrapper = getServerInstanceWrapper(managedServerName);
    	return wrapper != null ? wrapper.getWebBundleDeployerHelp() : null;
    }
    
}
